package PDFd;

import java.util.List;


import PDFd.Implementation.DrawingLayer;
import PDFd.Implementation.TextLayer;
import PDFd.Instance.PDFdInstance;
import PDFd.Layer.DrawingUtilities;
import PDFd.Layer.Layer;
import PDFd.Layer.LayerKeyEvent;
import PDFd.Layer.LayerMotionEvent;
import PDFd.Layer.Point;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.Toast;

/**
 * The view responsible for the majority of the work. This takes dispaches from other objects telling us what state to be in (Drawing, Text, etc)
 * Implemented internally as a state machine on @see ViewState
 * @author mckaym
 * TODO: All scaling of touch inputs needs to happen
 * TODO: If the device doesn't support pressure sensitivity, set a default
 * TODO: Do selective invalidates. Redrawing the whole canvas is slow.
 */
public class CanvasView extends View {
	public static final String TAG = "org.mattmckay.PDFd.CanvasView";
	/**
	 * The context used for displaying messages, dialogs, etc.
	 */
	public Context activityContext;
	/**
	 * What we're currently trying to do. @see ViewState
	 */
	private VeiwState thisState;
	/**
	 * Sets the working instance of the PDF we're rendering. CanvasView.setPDFdInstace()
	 */
	private static PDFdInstance instance;
	/**
	 * The PDF rendered as a bitmap. This is displayed as the lowest layer in a edited document
	 */
	private static Bitmap background;
	
	/**
	 * The axis by which the point is rotated
	 */
	private Point point;
	
	/**
	 * Sets the 'working' instance of a PDF we're rendering. This should be called before switching to this view.
	 * Calling this method when this view is active results in an IMMEADIATE update of the screen with the new PDFdInstace.
	 * So you should probably only call this when this View isn't active.
	 * @param instance The instance of a PDF we want to use
	 */
	public static void setPDFdInstance(PDFdInstance instance) {
		CanvasView.instance = instance;
		background = BitmapFactory.decodeByteArray(instance.getRenderedAsPNG(), 0, instance.getRenderedAsPNG().length);
	}
	public static PDFdInstance getPDFdInstance() {
		return instance;
	}
	/**
	 * The active layer index is the one that recieves event notifications.
	 * It also gets an outline drawn around it.
	 */
	Integer activeLayerIndex;
	
	private ScaleGestureDetector mScaleDetector;
	Point documentOffset;
	private float mLastTouchX;
	private float mLastTouchY;
	private float mScaleFactor = 1.f;
	
	public CanvasView(Context context) {
        this(context, null, 0);
	}
    
    public CanvasView(Context context, AttributeSet attrs) {
    	this(context, attrs, 0);
    }
    
    public CanvasView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        thisState = VeiwState.PanNScan;
	    mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
	    context = this.getContext();
	    documentOffset = new Point(0,0);
    }
    
    /**
     * Changes what mode the document is in. This is used to signal a new layer, modify a layer, pan n scan, etc
     * This should be the only way this view should be notified of state changes.
     * @param b The state of the UI
     */
    public void userStateChange(CanvasViewButtonStates b) {
    	switch(b) {
    		case View: {
    			thisState = VeiwState.PanNScan;
    			activeLayerIndex = null;
    			break;
    		}
    		case Draw: {
    			thisState = VeiwState.CreateLayer;
    			Layer l = new DrawingLayer();
    			l.setParentOffset(new Point(0,0));
    			l.setCanvasSize(new Point(0,0));
    			activeLayerIndex = instance.addLayer(l);
    			break;
    		}
    		case Text: {
    			thisState = VeiwState.CreateLayer;
    			Layer l = new TextLayer();
    			l.setParentOffset(new Point(0,0));
    			l.setCanvasSize(new Point(0,0));
    			activeLayerIndex = instance.addLayer(l);
    			break;
    		}
    		case Layer: {
    			/**
    			 * Displays a dialog box to the user allowing them to select a layer to edit
    			 * This then changes the active layer index and forces a redraw of the entire view
    			 * TODO: It would be handy to use small renderings of each layer in the dialog box so the user knows which layer is which.
    			 */
    			List<Layer> ls = instance.getLayers();
    			if(ls.size() == 0) {
    				Toast.makeText(activityContext, "No layers to select from =( ", Toast.LENGTH_SHORT).show();
    			} else {
    				
    			}
    			CharSequence[] items = new CharSequence[ls.size()];
    			for(int i = 0; i < ls.size(); i++) {
    				items[i] = i+" :"+ls.get(i).getDisplayName();
    			}
				AlertDialog.Builder builder = new AlertDialog.Builder(activityContext);
				builder.setTitle("Select a layer");
				builder.setItems(items, new DialogInterface.OnClickListener() {
					public void onClick(DialogInterface dialog, int item) {
						activeLayerIndex = item;
						invalidate();
						thisState = VeiwState.ModifyLayer;
						Toast.makeText(activityContext, "Layer "+item, Toast.LENGTH_SHORT).show();
					}
				});
				AlertDialog alert = builder.create();
				alert.show();
				
				/**
				 * We want to change back to the View state because once we select a layer we're going to want to be viewing it
				 * Also because if we stay in the Layer view we will just keep poping boxes up for the user to select a layer
				 */
    			userStateChange(CanvasViewButtonStates.View);
    			
    			break;
    		}
    	}
    	invalidate();
    }
    /**
     * Called by the android system, actually draws our document to the screen.
     */
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);

		canvas.save();
		//cover Pan N Scan settings
		canvas.translate(documentOffset.x, documentOffset.y);
		canvas.scale(mScaleFactor, mScaleFactor);

		//Fill the background with WHITE
		Paint paint = new Paint();
		paint.setStyle(Paint.Style.FILL);
		paint.setColor(Color.WHITE);
		canvas.drawPaint(paint);
		
		//Write the document over the background
		canvas.drawBitmap(background, 0, 0, null);

		
		/**
		 * If we have an active layer, do pretty active layer things.
		 */
		if(activeLayerIndex != null) {
			Layer activeLayer = instance.getLayer(activeLayerIndex);
			
			if(activeLayer.getCanvasSize().x > 0 && activeLayer.getCanvasSize().y > 0) {
				/**
				 * Draw the outline
				 */
				Bitmap outline = DrawingUtilities.drawOutline(activeLayer);
				if(outline != null) {
					Point offset = activeLayer.getParentOffset();
					canvas.drawBitmap(outline,offset.x,offset.y,null);
				}
			}
			/**
			 * If the layer wants keypresses, display the keyboard
			 * TODO: Hide the keyboard after it has been shown. 
			 * Is there a parameter that adds a "done" button to the keyboard?
			 * I know there is for IPhone. Maybe we can do something like that
			 * or even add a new button for keyboard dismissal, that gets displayed
			 * at the same time as keyboard?
			 */
			if(activeLayer instanceof LayerKeyEvent) {
				if(((LayerKeyEvent) activeLayer).showKeyboard()) {
					InputMethodManager mgr = (InputMethodManager) activityContext.getSystemService(Context.INPUT_METHOD_SERVICE);
					mgr.toggleSoftInput(InputMethodManager.SHOW_FORCED,0);
				}
			}
		}
		//Render all layers
		for(Layer l : instance.getLayers()) {
			l.rasterize(canvas);
		}
		canvas.restore();
	}
	/**
	 * Monitor keypresses for passing off to the active layer.
	 */
	public boolean onKeyDown(int keyCode, KeyEvent e) {
		if(activeLayerIndex != null) {
			Layer l = instance.getLayer(activeLayerIndex);
			if(l instanceof LayerKeyEvent) {
				((LayerKeyEvent) l).keyEvent(keyCode,e);
			}
		}
		return true;
	}
	
	/**
	 * Monitor touch events for passing off to the active layer, or moving around
	 */
	public boolean onTouchEvent(MotionEvent ev) {
		//Dispatch event to appropriate actions, based on thisState
		switch(thisState) {
			case PanNScan: {
				doPanNScan(ev);
				break;
			}
			case CreateLayer: {
				doCreateLayer(ev);
				break;
			}
			case ModifyLayer: {
				if(activeLayerIndex != null) {
					Layer l = instance.getLayer(activeLayerIndex);
					if(l instanceof LayerMotionEvent) {
						((LayerMotionEvent) l).setScaleFactor(mScaleFactor);
						((LayerMotionEvent) l).setViewOffset(documentOffset);
						((LayerMotionEvent) l).motionEvent(ev);
					}
				}
				break;
			}
		}
		//Force a redraw
		invalidate();
		return true;
	}
	/**
	 * A defaultable pointer that is safe to use when working with MotionEvents
	 */
	private static final int INVALID_POINTER = -1;
	/**
	 * This keeps track of which finger is responsible for creating a layer
	 */
	private int createLayerPointer = INVALID_POINTER;
	/**
	 * MotionEvents are dispatched to this when we are trying to create a new layer
	 */
	private void doCreateLayer(MotionEvent ev) {
		final int action = ev.getAction();
		Layer l = instance.getLayer(activeLayerIndex);
		int pointerIndex = ev.findPointerIndex(createLayerPointer);
			switch(action) {
			//Action_down will be them starting to size the box, so the parent offset
			case MotionEvent.ACTION_DOWN: {
				createLayerPointer = ev.getPointerId(0);
				Point p = new Point
					((int)((ev.getX()-documentOffset.x)/mScaleFactor), 
					(int)((ev.getY()-documentOffset.y)/mScaleFactor));
				point = p;
				l.setParentOffset(p);
				//store this point to calculate new top left corner for reverse layer drawing
				point = p;
				break;
			}
			/**
			 * Move and up are changing the size of the document.
			 * Up also finalizes the layer size
			 */
			case MotionEvent.ACTION_MOVE: {
				if(pointerIndex != INVALID_POINTER) {
					Point p = new Point
						((int)((ev.getX()-documentOffset.x)/mScaleFactor), 
						(int)((ev.getY()-documentOffset.y)/mScaleFactor));
					//do the validity test as soon as we know the value of p.
						//find the upper-left-most coordinates
					Point newParentOffset = new Point(Math.min(p.x, point.x), Math.min(p.y, point.y));
					Point pNew;
					//calculate size from the upper-left-most to the lower-right-most coordinates
					pNew = newParentOffset.difference(Math.max(p.x, point.x), Math.max(p.y, point.y));
					if (!point.equals(newParentOffset))
					l.setParentOffset(newParentOffset);
					l.setCanvasSize(pNew);
					break;
				}
			}
			case MotionEvent.ACTION_UP: {
				if(pointerIndex != INVALID_POINTER) {
					Point p = new Point
						((int)((ev.getX()-documentOffset.x)/mScaleFactor), 
						(int)((ev.getY()-documentOffset.y)/mScaleFactor));//do the validity test as soon as we know the value of p.
						//find the upper-left-most coordinates
					Point newParentOffset = new Point(Math.min(p.x, point.x), Math.min(p.y, point.y));
					Point pNew;
					//calculate size from the upper-left-most to the lower-right-most coordinates
					pNew = newParentOffset.difference(Math.max(p.x, point.x), Math.max(p.y, point.y));
					if (!point.equals(newParentOffset))
						l.setParentOffset(newParentOffset);
					l.setCanvasSize(pNew);
					//If the point is the same as the axis, we have a 'zero' sized layer.
					if(pNew.x == 0 || pNew.y == 0) {
						//Remove from active layers
						instance.layers.remove(l);
						activeLayerIndex = null;
					} else {
						l.finalizeSizeAndOffset();
					}
					thisState = VeiwState.ModifyLayer;
					pointerIndex = INVALID_POINTER;
				}
				break;
			}
		}
	}
	/**
	 * Responsible for moving our view around.
	 */
	private void doPanNScan(MotionEvent ev) {
	    mScaleDetector.onTouchEvent(ev);
		final int action = ev.getAction();
		switch (action) {
			case MotionEvent.ACTION_DOWN: {
				final float x = ev.getX();
				final float y = ev.getY();
	
				// Remember where we started
				mLastTouchX = x;
				mLastTouchY = y;
				break;
			}
	
			case MotionEvent.ACTION_MOVE: {
				final float x = ev.getX();
				final float y = ev.getY();
	
				// Calculate the distance moved
				final float dx = x - mLastTouchX;
				final float dy = y - mLastTouchY;
	
				// Move the object
				documentOffset.x += dx;
				documentOffset.y += dy;
				if(documentOffset.x  > 0) {
					documentOffset.x  = 0;
				}
				if(documentOffset.y > 0) {
					documentOffset.y = 0;
				}
				// Remember this touch position for the next move event
				mLastTouchX = x;
				mLastTouchY = y;
	
				// Invalidate to request a redraw
				invalidate();
				break;
			}
		}
	}

	private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
	    @Override
	    public boolean onScale(ScaleGestureDetector detector) {
	        mScaleFactor *= detector.getScaleFactor();
	        
	        // Don't let the object get too small or too large.
	        mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f));
	
	        invalidate();
	        return true;
	    }
	}
}
